package com.example.forjava.https;

import com.example.forjava.exception.DoStepException;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultClientConnectionReuseStrategy;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.util.concurrent.TimeUnit;

/**
 * @author vuser17
 */
//@Component
public class RemoteClient implements InitializingBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(RemoteClient.class);
    @Resource
    private HttpsConfig httpsConfig;
    private CloseableHttpClient client;
    private Executor executor;

    public String postJson(HttpsMessage message) {
        try {
            Request request = Request.Post(message.getHostAddress()).bodyString(message.getRequestMessage(), ContentType.APPLICATION_JSON);
            return executor.execute(request).handleResponse(response -> {
                if (HttpStatus.OK.value() != response.getStatusLine().getStatusCode()) {
                    LOGGER.error("响应状态：{}，{}", response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase());
                    throw new RuntimeException("9999");
                }
                return EntityUtils.toString(response.getEntity(), message.getCharSet());
            });
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception ex) {
            throw new DoStepException("9999", ex);
        }
    }

    @Override
    public void afterPropertiesSet() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(httpsConfig.getReadTimeOut())
                .setConnectTimeout(httpsConfig.getConnectionTimeOut())
                .setConnectionRequestTimeout(httpsConfig.getConnectionRequestTimeOut())
                .build();
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(buildSslContext(),
                NoopHostnameVerifier.INSTANCE);
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("https", sslConnectionSocketFactory)
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .build();
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connectionManager.setMaxTotal(httpsConfig.getConnectionMaxTotal());
        connectionManager.setDefaultMaxPerRoute(httpsConfig.getConnectionMaxPerRoute());
        connectionManager.setValidateAfterInactivity(httpsConfig.getConnectionValidateAfterInactivity());
        connectionManager.setDefaultSocketConfig(SocketConfig.custom().setTcpNoDelay(true).build());
        client = HttpClients.custom()
                .setSSLSocketFactory(sslConnectionSocketFactory)
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .setConnectionManagerShared(false)
                .disableAutomaticRetries()
                .evictIdleConnections(httpsConfig.getConnectionMaxIdleTime(), TimeUnit.MILLISECONDS)
                .setConnectionReuseStrategy(DefaultClientConnectionReuseStrategy.INSTANCE)
                .setKeepAliveStrategy((response, context) -> {
                    final HeaderElementIterator it = new BasicHeaderElementIterator(
                            response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                    while (it.hasNext()) {
                        final HeaderElement he = it.nextElement();
                        final String param = he.getName();
                        final String value = he.getValue();
                        if (value != null && param.equalsIgnoreCase("timeout")) {
                            try {
                                return Long.parseLong(value) * 1000;
                            } catch (final NumberFormatException ignore) {
                                // ignore
                            }
                        }
                    }
                    return httpsConfig.getConnectionTimeToLive();
                })
                .build();
        executor = Executor.newInstance(client);
    }

    @PreDestroy
    public void destroy() {
        try {
            LOGGER.debug("正在关闭HttpClient");
            client.close();
            LOGGER.debug("HttpClient关闭完成");
        } catch (Exception e) {
            LOGGER.error("关闭HttpClient失败：" + e.getMessage(), e);
        }
    }

    private SSLContext buildSslContext() {
        try {
            SSLContextBuilder builder = SSLContexts.custom();
            if (StringUtils.isEmpty(httpsConfig.getKeyStorePassword())
                    && StringUtils.isEmpty(httpsConfig.getKeyStorePath())) {
                return builder.loadTrustMaterial((cert, authType) -> true).build();
            }
            return builder.loadTrustMaterial(new File(httpsConfig.getKeyStorePath()), httpsConfig.getKeyStorePassword().toCharArray()).build();
        } catch (Exception ex) {
            throw new DoStepException("9999", ex);
        }
    }
}
